/**
 * Nakov Chat Server
 * (c) Svetlin Nakov, 2002
 *
 * ServerDispatcher class is purposed to listen for messages received
 * from clients and to dispatch them to all the clients connected to the
 * chat server.
 */

import java.net.*;
import java.util.*;

public class ServerDispatcher extends Thread
{
    private Vector mMessageQueue = new Vector();
    private Vector mClients = new Vector();

    /**
     * Adds given client to the server's client list.
     */
    public synchronized void addClient(ClientInfo aClientInfo)
    {
        mClients.add(aClientInfo);
    }
    /**
     * Deletes given client from the server's client list
     * if the client is in the list.
     */
    public synchronized void deleteClient(ClientInfo aClientInfo)
    {
        for (int i=0; i<mClients.size(); i++) {
           ClientInfo clientInfo = (ClientInfo) mClients.get(i);
           /*
            * Only send to clients logged in and not to the client who is about
            * to be deleted
            */
           if(clientInfo.loggedIn && 
                   !(clientInfo.UserName.matches(aClientInfo.UserName))){
               clientInfo.mClientSender.sendMessage("LEAVE " + 
                       aClientInfo.UserName);
            }
        }
        int clientIndex = mClients.indexOf(aClientInfo);
        if (clientIndex != -1)
           mClients.removeElementAt(clientIndex);
    }
    /**
     * Adds given message to the dispatcher's message queue and notifies this
     * thread to wake up the message queue reader (getNextMessageFromQueue method).
     * dispatchMessage method is called by other threads (ClientListener) when
     * a message is arrived.
     */
    public synchronized void dispatchMessage(ClientInfo aClientInfo, String aMessage)
    {
        String UserName = aClientInfo.UserName;

        /*
         * NAMES command for requesting the list of usernames.
         */
        if(aMessage.matches("NAMES")){
            String names = "+User list:\r\n";
            for (int i = 0; i < mClients.size(); i++)
            {
                ClientInfo clientInfo = (ClientInfo)mClients.get(i);
                /*
                 * Only those who are logged in are added to the list.
                 */
                if(clientInfo.loggedIn)
                    names = names + clientInfo.UserName + "\r\n";
            }
            aClientInfo.mClientSender.sendMessage(names);
        }

        /*
         * HELP command for requesting a list of available commands and what
         * they do
         */
        else if(aMessage.matches("HELP"))
        {
            String commands = "+ commands:\r\n";
            commands += "NAMES: requests a list of current clients.\r\n";
            commands += "HELP: requests a list of available commands and their"
                    +" uses.\r\n";
            commands += "RENAME 'new username': change your username to the new"
                    + " username if it is available and consists only of "
                    + "allowed characters.";
            aClientInfo.mClientSender.sendMessage(commands);
        }

        /*
         * SAY command to dispatch messages to everyone.
         */
        else if(aMessage.startsWith("SAY")){
            String message = aMessage.substring(4);
            aMessage = "SAY " + UserName + "/" + message;
            mMessageQueue.add(aMessage);
            /*
             * notifies the thread that there's a message in the queue
             */
            notify();
        }

        /*
         * RENAME command to change the username
         */
        else if(aMessage.startsWith("USER ")){
            String newName = aMessage.substring(5);
            if(!newName.matches("\\W+") && newName.matches("\\w+")){
                boolean nameInUse = false;
                String[] names = UsernameList().split("\\s");
                for(int i = 0; i < names.length; i++){
                    if(newName.matches(names[i])){
                        nameInUse = true;
                        break;
                    }
                }
                if(!nameInUse){
                    String oldName = aClientInfo.UserName;
                    aClientInfo.UserName = newName;
                    for (int i = 0; i < mClients.size(); i++)
                    {
                        ClientInfo clientInfo = (ClientInfo)mClients.get(i);
                        if(clientInfo.loggedIn &&
                                !(clientInfo.UserName.matches(aClientInfo.UserName))){
                            clientInfo.mClientSender.sendMessage("RENAME " 
                                    + oldName + "/" + newName);
                        }
                    }
                    notify();
                }
                else{
                    aClientInfo.mClientSender.sendMessage("-Username in use");
                }
            }
            else{
                aClientInfo.mClientSender.sendMessage("Invalid user name "
                        + "only use[a-zA-Z_0-9]");
            }
        }
        else{
               aClientInfo.mClientSender.sendMessage("verkeerde invoer");
               System.out.println("in server dispatcher");
        }    
    }

    /**
     * @return and deletes the next message from the message queue. If there is no
     * messages in the queue, falls in sleep until notified by dispatchMessage method.
     */
    private synchronized String getNextMessageFromQueue()
    throws InterruptedException
    {
        while (mMessageQueue.isEmpty())
           wait();
        String message = (String) mMessageQueue.get(0);
        mMessageQueue.removeElementAt(0);
        return message;
    }
    
    /*
     * Sends given message to all clients in the client list. Actually the
     * message is added to the client sender thread's message queue and this
     * client sender thread is notified.
     */
    private synchronized void sendMessageToAllClients(String aMessage)
    {
        for (int i=0; i<mClients.size(); i++) {
           ClientInfo clientInfo = (ClientInfo) mClients.get(i);
           if(clientInfo.loggedIn)
               clientInfo.mClientSender.sendMessage(aMessage);
        }
    }

    /*
     * Sends a notification to all clients that another client has joined the
     * server.
     */
    public synchronized void JoinNotify(ClientInfo aClientInfo)
    {
        for (int i=0; i<mClients.size(); i++) {
           ClientInfo clientInfo = (ClientInfo) mClients.get(i);
           if(clientInfo.loggedIn &&
                   !(clientInfo.UserName.matches(aClientInfo.UserName))){
               clientInfo.mClientSender.sendMessage("JOIN " +
                       aClientInfo.UserName);
            }
        }
    }

    /*
     * makes a list of all the usernames currently in use.
     */
    public synchronized String UsernameList()
    {
        String nameList = "";
        for (int i=0; i<mClients.size(); i++) {
           ClientInfo clientInfo = (ClientInfo) mClients.get(i);
           if(clientInfo.loggedIn)
               nameList += clientInfo.UserName + " ";
        }
        return nameList;
    }
    
    /*
     * Infinitely reads messages from the queue and dispatch them
     * to all clients connected to the server.
     */
    public void run()
    {
        try {
            while (true)
            {
            String message = getNextMessageFromQueue();
            sendMessageToAllClients(message);

            }
        } catch (InterruptedException ie) {
           // Thread interrupted. Stop its execution
        }
    }
}